// --------------------------------------------------------------------------------------------------------------------
// <copyright file="RunnerBase.cs" company="">
//   
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace ExecutionTool.Common
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;

    using ExecutionTool.CopyDatabase;
    using ExecutionTool.CopyOnPremises;
    using ExecutionTool.ImportExport;
    using ExecutionTool.Profile;

    using Microsoft.Practices.EnterpriseLibrary.Logging;
    using Microsoft.Practices.ServiceLocation;

    public abstract class RunnerBase
    {
        #region Fields

        protected readonly IImportExportServiceAdatper ImportExportServiceAdapter;

        protected readonly IOnPremisesAdapter OnPremisesAdapter;

        protected readonly CommandLineOptions Options;

        protected readonly ISqlAzureAdapter SqlAzureAdapter;

        protected readonly IStorageAdapter StorageAdapter;

        private readonly IList<IStep> steps;

        #endregion

        #region Constructors and Destructors

        protected RunnerBase(CommandLineOptions options)
        {
            Helper.CheckForNull(options, "options");

            this.Options = options;
            this.SqlAzureAdapter = new SqlAzureAdapter();
            this.ImportExportServiceAdapter = new ImportExportServiceAdatper();
            this.StorageAdapter = new StorageAdapter();
            this.OnPremisesAdapter = new OnPremisesAdapter();

            this.steps = new List<IStep>();
        }

        #endregion

        #region Properties

        protected LogWriter Logger
        {
            get
            {
                return ServiceLocator.Current.GetInstance<LogWriter>();
            }
        }

        #endregion

        #region Public Methods and Operators

        public void Run()
        {
            /*
             * load profile
               - validate profile
             * 
             * test connection if required by using arg
             */
            var p = ProfileFacade.LoadProfile(this.Options.ProfileName);

            try
            {
                this.TestConnectivity(p);
            }
            catch (Exception ex)
            {
                throw new StepFaultException(
                    "Connectivity test failed, please check inner exception for details, fix the problem and re-run the operation.", 
                    StepType.TestConnection, 
                    ErrorCodes.UnexpectedTestConnectionError, 
                    ex);
            }

            AppDomain.CurrentDomain.ProcessExit += (sender, args) =>
                {
                    foreach (var step in this.steps.Where(step => step.CanAbort && step.StepState == StepState.Running))
                    {
                        step.Abort();
                    }
                };

            this.RunAction(p);
        }

        public void TestConnectivity(Profile profile)
        {
            this.TestConnectivityAction(profile);
        }

        #endregion

        #region Methods

        protected abstract void RunAction(Profile profile);

        protected void RunStep<T>(IStep<T> step, StepType stepType, out T info)
        {
            const string RunStepFailureMessage = "Failed to run a step. Please check inner exception for details. --> {1}";
            var e = new ManualResetEvent(false);
            var stepName = step.GetType().Name;
            step.Progressed += (sender, args) => this.Logger.LogInformation(string.Format("{0}: Progress = {1}%", stepName, args.Progress));
            FaultedEventArgs faultedEventArgs = null;
            step.Faulted += (sender, args) =>
                {
                    this.Logger.LogWarning(string.Format("{0}: Failed to run at location - [{1}].", stepName, args.FaultedLocation));
                    faultedEventArgs = args;
                    e.Set();
                };

            step.Finished += (sender, args) =>
                {
                    this.Logger.LogInformation(string.Format("{0}: Completed.", stepName));
                    e.Set();
                };

            this.steps.Add(step);
            info = step.Start();
            e.WaitOne();
            if (step.StepState != StepState.Finished && faultedEventArgs != null)
            {
                var sfe = faultedEventArgs.Exception as StepFaultException;

                if (sfe != null)
                {
                    throw sfe;
                }

                throw new StepFaultException(
                    RunStepFailureMessage, 
                    stepType, 
                    faultedEventArgs.ErrorCode == 0 ? ErrorCodes.UnexpectedError : faultedEventArgs.ErrorCode, 
                    faultedEventArgs.Exception);
            }
        }

        protected abstract void TestConnectivityAction(Profile profile);

        #endregion
    }
}
